#define _CRT_SECURE_NO_WARNINGS 1

#include"CentralCache.h"
#include"PageCache.h"

//定义，饿汉模式
CentralCache* CentralCache::_pInst = new CentralCache();

//获取一个非空的span
Span* CentralCache::GetOneSpan(SpanList& list, size_t size)
{
	//查看当前的spanlist这时是否有 未分配对象的span
	Span* it = list.Begin();
	while (it != list.End())
	{
		if (it->_freeList != nullptr)
		{
			return it;
		}
		else
		{
			it = it->_next;
		}
	}
	//先把CentralCache的桶锁解掉，这样如果其他线程释放内存对象回来，不会阻塞
	list._mtx.unlock();

	//走到此说明没有空闲span了，只能找PageCache要
	PageCache::GetInstance()->_pageMtx.lock();
	Span* span=PageCache::GetInstance()->NewSpan(AMSize::NumMovePage(size));
	span->_isUse = true;
	span->_objSize = size;//(小于256Kb)三级缓存中一定是从PageCache中去拿，存储对象大小
	PageCache::GetInstance()->_pageMtx.unlock();

	//切分span并挂接到桶，此时不需要加锁，因为这会其他线程访问不到这个span
	//计算span的大块内存的起始地址和大块内存的大小（字节数）
	char* start = (char*)(span->_pageId << PAGE_SHIFT);
	size_t bytes = span->_n << PAGE_SHIFT;
	char* end = start + bytes;

	//把大块内存切成自由链表链接起来
	//先切一块下来做头，方便尾插，尾插是为了保存地址顺序
	span->_freeList = start;
	start += size;
	void* tail = span->_freeList;

	while (start < end)
	{
		NextObj(tail) = start;
		tail = NextObj(tail);
		start += size;
	}
	//最后一个span内的小内存块指向空
	NextObj(tail) = nullptr;

	//切好span后，挂接到桶需要加锁
	list._mtx.lock();
	list.PushFront(span);

	return span;
}

//从中心缓存获取一定数量的对象给Thread Cache
size_t CentralCache::FetchRangeObj(void*& start, void*& end, size_t batchNum, size_t size)
{
	size_t index = AMSize::Index(size);
	_spanLists[index]._mtx.lock();//加桶锁

	Span* span = GetOneSpan(_spanLists[index], size);
	assert(span);
	assert(span->_freeList);

	//从span中获取batchNum个对象，如果不够batchNum个，有多少拿多少
	end = start = span->_freeList;
	size_t i = 0;
	size_t actualNum = 1;
	while (i < batchNum - 1 && NextObj(end) != nullptr)
	{
		end = NextObj(end);
		++i;
		++actualNum;
	}
	//span中内存的自由链表指向分出后的余下内存结点
	span->_freeList = NextObj(end);
	//分出的最后个结点指向空
	NextObj(end) = nullptr;
	span->_useCount += actualNum;

	_spanLists[index]._mtx.unlock();

	return actualNum;
}

// 将一定数量的对象释放到span跨度
void CentralCache::ReleaseListToSpans(void* start, size_t size)
{
	//找到在哪个桶上
	size_t index = AMSize::Index(size);
	_spanLists[index]._mtx.lock();//加锁，因为有桶锁防止多线程竞争
	//回收到span
	while (start)
	{
		void* next = NextObj(start);
		//找到对应的span，小内存（自由链表）头插
		Span* span = PageCache::GetInstance()->MapObjToSpan(start);
		NextObj(start) = span->_freeList;
		span->_freeList = start;
		span->_useCount--;

		//如果为0，说明span切分出的小块内存都回来了，这个span可以再回收给PageCache，再尝试去前后页合并
		if (span->_useCount == 0)
		{
			//从桶里拿掉这个span
			_spanLists[index].Erase(span);
			//知道span的页号就可以知道span的起始地址从而找到整块span，不需要考虑小块内存链表_freeList了
			span->_freeList = nullptr;
			span->_next = nullptr;
			span->_prev = nullptr;

			_spanLists[index]._mtx.unlock();//已经拿掉span了，可以释放桶锁给别人

			PageCache::GetInstance()->_pageMtx.lock();
			PageCache::GetInstance()->ReleaseSpanToPageCache(span);
			PageCache::GetInstance()->_pageMtx.unlock();

			_spanLists[index]._mtx.lock();
		}

		start = next;
	}
	_spanLists[index]._mtx.unlock();
}